<cfcomponent displayname="CrafterPathCollection" accessors="yes" output="no" extends="crafter.CrafterDocumentCollection">
	
	
	<!--- Properties --->
	<cfproperty name="path" type="string" />
	<cfproperty name="filter" type="any" />
	<cfproperty name="filterName" type="string" />
	<cfproperty name="crafterFactory" type="any" />
	<cfproperty name="collectionIsLoaded" type="boolean" />
	<cfproperty name="navTree" type="any" />
	
	
	<!--- init() --->
	<cffunction name="init" access="public" returntype="any" output="no">
		<cfargument name="crafterFactory" type="any" required="yes" />
		<cfargument name="collectionName" type="string" required="no" default="" />
		<cfscript>
			
			// This object extends the CrafterCollection object. So, we need
			// to run its init() method as well.
			super.init(
				crafterFactory = arguments.crafterFactory,
				collectionName = arguments.collectionName	   
			);
			
			setCollectionIsLoaded(false);
			return this;
			
		</cfscript>
	</cffunction>
	
	
	<!--- load() --->
	<cffunction name="load" access="public" returntype="any" output="no">
		<cfargument name="path" type="string" required="yes" />
		<cfargument name="filterName" type="string" required="no" default="" />
		<cfargument name="depth" type="numeric" required="no" default="1" />
		
		<cfscript>
			
			// Set properties
			setPath(arguments.path);
			setFilterName(arguments.filterName);
			
			// Look to see if a filter name was passed in.  If so, load
			// that filter.  If not, load the fallback filter.
			if (arguments.filterName NEQ "") {
				local.oFilter = getCrafterFactory().getFilter(arguments.filterName);
				setFilter(local.oFilter);
			};
			
			// Using the StructureAccessor from the Factory object and the filter
			// that's been passed in, load the Crafter Nav Tree
			local.oNavTree = getCrafterFactory().getStructureAccessor().getNavTree(arguments.path, arguments.depth, getFilter());
			setNavTree(local.oNavTree);
			
			
			// Look to see if the depth is greater than 1. If so, we need to identify if there are any folders in
			// this nav tree and recursively work through them to flatten out the contained files into a single
			// array
			if (arguments.depth GT 1) {
				local.arWorkingCollection = recurseAndFlattenNavTree(
					navTree = getNavTree().getChildren(),
					currentDepth = 1, 
					depthLimit = arguments.depth
				);	
			}
			else {
				local.arWorkingCollection = getNavTree().getChildren();
			}
			
			/*
				At this point, local.arWorkingCollection is an array that's going to represent our collection. To minimize
				up-front processing and memory footprint, a CrafterDocumentCollection is going to be
				hybrid.  We will replace the NavTreeImpl objects with CrafterDocument objects lazily as they
				are needed, this way we're doing less work.
			*/

			local.arSizedArray = ArrayNew(1);
			setCollection(local.arWorkingCollection);
			setCollectionLength(ArrayLen(getCollection()));
			
			
			// Mark as loaded
			setCollectionIsLoaded(true);
			return this;		
		</cfscript>
	</cffunction>
	
	
	<!--- recurseAndFlattenNavTree() --->
	<cffunction name="recurseAndFlattenNavTree" access="private" output="no" returntype="any">
		<cfargument name="navTree" type="any" required="yes" />
		<cfargument name="currentDepth" type="numeric" required="yes" />
		<cfargument name="depthLimit" type="numeric" required="yes" />
		<cfscript>
		
			// Set up placeholder array/collection
			local.arWorkingCollection = ArrayNew(1);
			
			// Loop over the navTree
			for (local.i = 1; local.i LTE ArrayLen(arguments.navTree); local.i++) {
				// See if the navTreeItem is a folder
				if ((arguments.navTree[local.i].getNavItem().isFolder()) AND (arguments.currentDepth LTE arguments.depthLimit)) {
					// Grab the children of this folder and pass this back to the function
					// as the new baseline navTree
					local.oNextNavTree = arguments.navTree[local.i].getChildren();
					if (IsDefined("local.oNextNavTree")) {
						
						local.arLevelResults = recurseAndFlattenNavTree(
							navTree = local.oNextNavTree,
							currentDepth = (arguments.currentDepth+1),
							depthLimit = arguments.depthLimit
						);
						
						local.arWorkingCollection.addAll(local.arLevelResults);
					}
				}
				else {
					// Not a folder. Append to file collection.	
					ArrayAppend(local.arWorkingCollection, arguments.navTree[local.i]);
				}
			}
			
			return local.arWorkingCollection;
		</cfscript>
		
	</cffunction>
	
	
	<!--- requireFilterProperty() --->
	<cffunction name="requireFilterProperty" access="public" output="no" returntype="any">
		<cfargument name="property" type="string" required="yes" />
		<cfargument name="value" type="string" required="yes" />
		<cfscript>
			local.arWorkingCollection = ArrayNew(1);
			for (local.i = 1; local.i LTE getCollectionLength(); local.i++) {
				local.oItem = lazyGetItem(local.i);
				local.stProps = local.oItem.getNavItem().getProperties();
				if (StructKeyExists(local.stProps, arguments.property) AND (StructFind(local.stProps, arguments.property) EQ arguments.value)) {
					ArrayAppend(local.arWorkingCollection, local.oItem);															   
				}
			}
			replaceCollection(local.arWorkingCollection);
			return this;
		</cfscript>
	</cffunction>
	
	
	<!--- requireFilterXPath() --->
	<cffunction name="requireFilterXPath" access="public" output="no" returntype="any">
		<cfargument name="xpath" type="string" required="yes" />
		<cfargument name="value" type="string" required="no" default="" />
		<cfargument name="matchType" type="string" required="no" default="exists" />
		<cfscript>
			local.arWorkingCollection = ArrayNew(1);
			for (local.i = 1; local.i LTE getCollectionLength(); local.i++) {
				local.oItem = lazyGetItem(local.i);
				local.stProps = local.oItem.getNavItem().getProperties();
				
				
				// If the matchType is exists, simply confirm that the property is present
				if (arguments.matchType EQ "exists") {
					if (StructKeyExists(local.stProps, arguments.xpath)) {
						ArrayAppend(local.arWorkingCollection, local.oItem);		
					}
				}
				// If matchType = match the property must exists and the value
				// of the property must match the value argument
				else if (arguments.matchType EQ "match") {
					if (StructKeyExists(local.stProps, arguments.xpath) AND (StructFind(local.stProps, arguments.xpath) EQ arguments.value)) {
						ArrayAppend(local.arWorkingCollection, local.oItem);		
					}
				}
				else if (arguments.matchType EQ "contains") {
					if (StructKeyExists(local.stProps, arguments.xpath) AND (FindNoCase(arguments.value, StructFind(local.stProps, arguments.xpath)))) {
						ArrayAppend(local.arWorkingCollection, local.oItem);		
					}
				}	
			}
			return local.arWorkingCollection;
		</cfscript>
	</cffunction>
	
	
	<!--- requireSetting() --->
	<cffunction name="requireSetting" access="public" output="no" returntype="any">
		<cfargument name="xpath" type="string" required="yes" />
		<cfscript>
			local.arNewCollection = requireFilterXPath(
				xpath = arguments.xpath,
				matchType = "exists"
			);
			replaceCollection(local.arNewCollection);
			return this;
		</cfscript>
	</cffunction>
	
	
	<!--- requireTags() --->
	<cffunction name="requireTags" access="public" returntype="any" hint="Loops through a document collection and removes documents which do not meet the tag requirements." output="no">
		<cfargument name="tagList" type="string" required="yes" />
		<cfargument name="tagType" type="string" required="no" default="system" />
		<cfscript>
			local.arWorkingCollection = ArrayNew(1);
			for (local.i = 1; local.i LTE ListLen(arguments.tagList); local.i++) {
				local.arNewCollection = requireFilterXPath(
					xpath = "//tagging/tagGroup[productCode=""#getCrafterFactory().getProductCode()#""]/#tagType#Tags",
					value = ListGetAt(arguments.tagList, i),
					matchType = "contains"
				);
				local.arWorkingCollection.addAll(local.arNewCollection);
			}
			replaceCollection(local.arWorkingCollection);
			return this;
		</cfscript>
	</cffunction>
	
	
	<!--- sort() --->
	<cffunction name="sort" access="public" returntype="any" output="no">
		<cfargument name="direction" type="string" required="no" default="ASC" />
		<cfscript>
			local.arPathStringCollection = ArrayNew(1);
			local.arOriginalCollection = getCollection();
			local.arOrderedObjectCollection = ArrayNew(1);
			local.stIndexMap = StructNew();
			
			for (local.i = 1; local.i LTE ArrayLen(local.arOriginalCollection); local.i++) {
				ArrayAppend(local.arPathStringCollection, local.arOriginalCollection[i].getNavItem().getItemURL());
				local.stIndexMap["#local.arOriginalCollection[i].getNavItem().getItemURL()#"] = i;
			}
			
			ArraySort(local.arPathStringCollection, "textnocase", arguments.direction);
			
			for (local.i = 1; local.i LTE ArrayLen(local.arPathStringCollection); local.i++) {
				local.itemIndex = local.stIndexMap["#local.arPathStringCollection[i]#"];
				ArrayAppend(local.arOrderedObjectCollection, lazyGetItem(local.itemIndex));
			}
			
			setCollection(local.arOrderedObjectCollection);
			return this;
		</cfscript>
	</cffunction>
	
	
	<!--- mergePathCollections() --->
	<cffunction name="mergePathCollections" access="public" returntype="any" output="no">
		<cfargument name="collection" type="crafter.CrafterPathCollection" required="yes" />
		<cfscript>
		
			local.arFileNames = ArrayNew(1);

			//Append the collection to the first collection
			local.arWorkingCollection = ArrayNew(1);
			local.arWorkingCollection.addAll(getCollection());
			local.arWorkingCollection.addAll(arguments.collection.getCollection());

			// From the working array make an array of only filenames (no paths)
			for (local.i = 1; local.i LTE ArrayLen(local.arWorkingCollection); local.i++) {
				local.sFilePath = CrafterFactory.getFileFromFilePath(true, local.arWorkingCollection[local.i].getNavItem().getItemUrl());
				ArrayAppend(local.arFileNames, local.sFilePath);
			}
			
			// Copy the filename array.  The difference between the original filename array and the sorted
			// filename array will allow us to re-order the object collection
			local.arSortedFileNames = local.arFileNames.clone();
			ArraySort(local.arSortedFileNames, "textnocase", "desc");
			
			//Sort the final collection according to the sorted array of file names
			local.arFinalCollection = ArrayNew(1);
			
			for (local.x = 1; local.x LTE ArrayLen(local.arSortedFileNames); local.x++) {
				local.nOriginalIndex = ArrayFind(local.arFileNames, local.arSortedFileNames[local.x]);
				local.arFinalCollection[local.x] = local.arWorkingCollection[local.nOriginalIndex];
			}
						
			return local.arFinalCollection;
		</cfscript>
	</cffunction>
	
	
	
</cfcomponent>